﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="DatePicker.cs" company="Vlady-mix">
//   Vlady-mix 2015
// </copyright>
// <summary>
//   Represents a page used by the DatePicker control that allows the user to choose a date (day/month/year).
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace CustomDatePicker.Controls.DatePicker
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;

    using Windows.Foundation;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Input;

    #region Public Enum

    /// <summary>
    ///     The scroll action.
    /// </summary>
    public enum ScrollAction
    {
        /// <summary>
        ///     The down.
        /// </summary>
        Down,

        /// <summary>
        ///     The up.
        /// </summary>
        Up
    }

    /// <summary>
    ///     The focus source type.
    /// </summary>
    public enum FocusSourceType
    {
        /// <summary>
        ///     The tap.
        /// </summary>
        Tap,

        /// <summary>
        ///     The pointer.
        /// </summary>
        Pointer,

        /// <summary>
        ///     The keryboard.
        /// </summary>
        Keryboard,

        /// <summary>
        ///     The manipulation.
        /// </summary>
        Manipulation,

        /// <summary>
        ///     The un focus.
        /// </summary>
        UnFocus
    }

    /// <summary>
    ///     The data source type.
    /// </summary>
    public enum DataSourceType
    {
        /// <summary>
        ///     The year.
        /// </summary>
        Year,

        /// <summary>
        ///     The month.
        /// </summary>
        Month,

        /// <summary>
        ///     The day.
        /// </summary>
        Day,

        /// <summary>
        ///     The hour.
        /// </summary>
        Hour,

        /// <summary>
        ///     The minute.
        /// </summary>
        Minute,

        /// <summary>
        ///     The second.
        /// </summary>
        Second
    }

    #endregion

    /// <summary>
    ///     Represents a page used by the DatePicker control that allows the user to choose a date (day/month/year).
    /// </summary>
    [TemplatePart(Name = PrimarySelectorName, Type = typeof(PickerSelector))]
    [TemplatePart(Name = SecondarySelectorName, Type = typeof(PickerSelector))]
    [TemplatePart(Name = TertiarySelectorName, Type = typeof(PickerSelector))]
    public sealed class DatePicker : Control
    {
        #region Private Const

        /// <summary>
        ///     The primary selector name.
        /// </summary>
        private const string PrimarySelectorName = "PART_PrimarySelector";

        /// <summary>
        ///     The secondary selector name.
        /// </summary>
        private const string SecondarySelectorName = "PART_SecondarySelector";

        /// <summary>
        ///     The tertiary selector name.
        /// </summary>
        private const string TertiarySelectorName = "PART_TertiarySelector";

        #endregion

        #region Private Fields

        /// <summary>
        ///     The primary selector.
        /// </summary>
        private PickerSelector primarySelector;

        /// <summary>
        ///     The secondary selector.
        /// </summary>
        private PickerSelector secondarySelector;

        /// <summary>
        ///     The tertiary selector.
        /// </summary>
        private PickerSelector tertiarySelector;

        #endregion

        #region Constructor

        /// <summary>
        ///     Initializes a new instance of the <see cref="DatePicker" /> class.
        /// </summary>
        public DatePicker()
        {
            this.DefaultStyleKey = typeof(DatePicker);
            this.InitializationInProgress = true;
            this.LayoutUpdated += this.OnLayoutUpdated;
            this.Loaded += this.OnLoaded;
            this.OnApplyTemplate();
        }

        #endregion

        #region Public Methods

        /// <summary>
        ///     The reset pickers order.
        /// </summary>
        public void ResetPickersOrder()
        {
            // Position and reveal the culture-relevant selectors
            var column = 0;
            foreach (var selector in this.GetSelectorsOrderedByCulturePattern())
            {
                Grid.SetColumn(selector, column);
                selector.Visibility = Visibility.Visible;
                column++;
            }
        }

        #endregion

        #region Internal Fields

        /// <summary>
        ///     The small font size property.
        /// </summary>
        internal static readonly DependencyProperty SmallFontSizeProperty = DependencyProperty.Register(
            "SmallFontSize",
            typeof(Double),
            typeof(DatePicker),
            new PropertyMetadata(default(Double)));

        /// <summary>
        ///     The value property.
        /// </summary>
        internal static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
            "Value",
            typeof(DateTime),
            typeof(DatePicker),
            new PropertyMetadata(DateTime.Today, OnValueChanged));

        #endregion

        #region Public Properties

        /// <summary>
        ///     Gets or sets a value indicating whether initialization in progress.
        /// </summary>
        public bool InitializationInProgress
        {
            get;
            set;
        }

        /// <summary>
        ///     Gets or sets the small font size.
        /// </summary>
        public double SmallFontSize
        {
            get
            {
                return (double)this.GetValue(SmallFontSizeProperty);
            }

            set
            {
                this.SetValue(SmallFontSizeProperty, value);
            }
        }

        /// <summary>
        ///     Gets or sets a value indicating whether enable first tap has focus only.
        /// </summary>
        public bool EnableFirstTapHasFocusOnly
        {
            get
            {
                return (bool)this.GetValue(EnableFirstTapHasFocusOnlyProperty);
            }

            set
            {
                this.SetValue(EnableFirstTapHasFocusOnlyProperty, value);
            }
        }

        /// <summary>
        ///     Gets or sets the value.
        /// </summary>
        public DateTime Value
        {
            get
            {
                var t = this.GetValue(ValueProperty);
                if (t == null)
                {
                    this.SetValue(ValueProperty, DateTime.Today);
                }

                return (DateTime)this.GetValue(ValueProperty);
            }

            set
            {
                var dt = new DateTime(value.Year, value.Month, value.Day);
                this.SetValue(ValueProperty, dt);
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// On Tapped, make focus on the good PickerSelector
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected override void OnTapped(TappedRoutedEventArgs e)
        {
            this.RefreshRect();

            var point = e.GetPosition(this);
            this.FocusPickerSelector(point, FocusSourceType.Tap);

            PickerSelector selector = null;
            if (this.primarySelector != null && point.X > this.primarySelector.RectPosition.X
               && point.X < (this.primarySelector.RectPosition.X + this.primarySelector.RectPosition.Width))
            {
                selector = this.primarySelector;
            }

            if (this.secondarySelector != null && point.X > this.secondarySelector.RectPosition.X
               && point.X < (this.secondarySelector.RectPosition.X + this.secondarySelector.RectPosition.Width))
            {
                selector = this.secondarySelector;
            }

            if (this.tertiarySelector != null && point.X > this.tertiarySelector.RectPosition.X
               && point.X < (this.tertiarySelector.RectPosition.X + this.tertiarySelector.RectPosition.Width))
            {
                selector = this.tertiarySelector;
            }

            if (selector != null)
            {
                var dateTimeWrapper = selector.SelectedItem;

                if (dateTimeWrapper == null)
                {
                    return;
                }

                this.Value = dateTimeWrapper.DateTime;
            }
        }

        /// <summary>
        /// On Manipulation, make focus on the good PickerSelector
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected override void OnManipulationStarted(ManipulationStartedRoutedEventArgs e)
        {
            var itemsPanel = e.Container as LoopItemsPanel;
            if (itemsPanel == null)
            {
                return;
            }

            this.RefreshRect();

            // fake Position to get the correct PickerSelector
            var position = new Point(
                itemsPanel.ParentDatePickerSelector.RectPosition.X + 1,
                itemsPanel.ParentDatePickerSelector.RectPosition.Y + 1);

            this.FocusPickerSelector(position, FocusSourceType.Manipulation);
        }

        /// <summary>
        /// The on manipulation completed.
        /// </summary>
        /// <param name="e">
        /// The e.
        /// </param>
        protected override void OnManipulationCompleted(ManipulationCompletedRoutedEventArgs e)
        {
            var itemsPanel = e.Container as LoopItemsPanel;

            if (itemsPanel == null)
            {
                return;
            }

            var selectorItem = itemsPanel.GetMiddleItem();

            if (selectorItem == null)
            {
                return;
            }

            var dateTimeWrapper = selectorItem.DataContext as DateTimeWrapper;

            if (dateTimeWrapper == null)
            {
                return;
            }

            this.Value = dateTimeWrapper.DateTime;
        }

        /// <summary>
        ///     Override of OnApplyTemplate
        /// </summary>
        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            this.InitializationInProgress = true;

            this.primarySelector = this.GetTemplateChild(PrimarySelectorName) as PickerSelector;
            this.secondarySelector = this.GetTemplateChild(SecondarySelectorName) as PickerSelector;
            this.tertiarySelector = this.GetTemplateChild(TertiarySelectorName) as PickerSelector;

            // Create wrapper on current value
            var wrapper = new DateTimeWrapper(this.Value);

            // Init Selectors
            // Set Datasource 
            if (this.primarySelector != null)
            {
                this.primarySelector.DatePicker = this;
                this.primarySelector.YearDataSource = new YearDataSource();
                this.primarySelector.DataSourceType = DataSourceType.Year;
                this.primarySelector.CreateOrUpdateItems(wrapper.DateTime);
            }

            if (this.secondarySelector != null)
            {
                this.secondarySelector.DatePicker = this;
                this.secondarySelector.MonthDataSource = new MonthDataSource();
                this.secondarySelector.DataSourceType = DataSourceType.Month;
                this.secondarySelector.CreateOrUpdateItems(wrapper.DateTime);
            }

            if (this.tertiarySelector != null)
            {
                this.tertiarySelector.DatePicker = this;
                this.tertiarySelector.DayDataSource = new DayDataSource();
                this.tertiarySelector.DataSourceType = DataSourceType.Day;
                this.tertiarySelector.CreateOrUpdateItems(wrapper.DateTime);
            }

            this.ResetPickersOrder();

            this.InitializationInProgress = false;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// The on value changed.
        /// </summary>
        /// <param name="d">
        /// The d.
        /// </param>
        /// <param name="e">
        /// The e.
        /// </param>
        private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var ctrl = (DatePicker)d;

            if (ctrl.InitializationInProgress)
            {
                return;
            }

            if (e.OldValue == null)
            {
                return;
            }

            if (e.NewValue == e.OldValue)
            {
                return;
            }

            if (ctrl.primarySelector != null)
            {
                ctrl.primarySelector.CreateOrUpdateItems((DateTime)e.NewValue);
                ctrl.primarySelector.GetItemsPanel()
                    .ScrollToSelectedIndex(ctrl.primarySelector.GetSelectedPickerSelectorItem(), TimeSpan.Zero);
            }

            if (ctrl.secondarySelector != null)
            {
                ctrl.secondarySelector.CreateOrUpdateItems((DateTime)e.NewValue);
                ctrl.secondarySelector.GetItemsPanel()
                    .ScrollToSelectedIndex(ctrl.secondarySelector.GetSelectedPickerSelectorItem(), TimeSpan.Zero);
            }

            if (ctrl.tertiarySelector != null)
            {
                ctrl.tertiarySelector.CreateOrUpdateItems((DateTime)e.NewValue);
                ctrl.tertiarySelector.GetItemsPanel()
                    .ScrollToSelectedIndex(ctrl.tertiarySelector.GetSelectedPickerSelectorItem(), TimeSpan.Zero);
            }
        }

        /// <summary>
        /// The on loaded.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="routedEventArgs">
        /// The routed event args.
        /// </param>
        private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
        {
            if (this.secondarySelector != null)
            {
                this.secondarySelector.IsFocused = false;
            }

            if (this.tertiarySelector != null)
            {
                this.tertiarySelector.IsFocused = false;
            }

            if (this.primarySelector != null)
            {
                this.primarySelector.IsFocused = false;
            }
        }

        /// <summary>
        /// Get boundaries on each selector
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="o">
        /// The o.
        /// </param>
        private void OnLayoutUpdated(object sender, object o)
        {
            if (this.primarySelector != null)
            {
                this.primarySelector.RectPosition =
                    this.primarySelector.TransformToVisual(this)
                        .TransformBounds(
                            new Rect(0, 0, this.primarySelector.DesiredSize.Width, this.primarySelector.DesiredSize.Height));
            }

            if (this.secondarySelector != null)
            {
                this.secondarySelector.RectPosition =
                    this.secondarySelector.TransformToVisual(this)
                        .TransformBounds(
                            new Rect(
                                0,
                                0,
                                this.secondarySelector.DesiredSize.Width,
                                this.secondarySelector.DesiredSize.Height));
            }

            if (this.tertiarySelector != null)
            {
                this.tertiarySelector.RectPosition =
                    this.tertiarySelector.TransformToVisual(this)
                        .TransformBounds(
                            new Rect(
                                0,
                                0,
                                this.tertiarySelector.DesiredSize.Width,
                                this.tertiarySelector.DesiredSize.Height));
            }

            this.LayoutUpdated -= this.OnLayoutUpdated;
        }

        /// <summary>
        ///     The refresh rect.
        /// </summary>
        private void RefreshRect()
        {
            if (this.primarySelector != null)
            {
                this.primarySelector.RectPosition =
                    this.primarySelector.TransformToVisual(this)
                        .TransformBounds(
                            new Rect(0, 0, this.primarySelector.DesiredSize.Width, this.primarySelector.DesiredSize.Height));
            }

            if (this.secondarySelector != null)
            {
                this.secondarySelector.RectPosition =
                    this.secondarySelector.TransformToVisual(this)
                        .TransformBounds(
                            new Rect(
                                0,
                                0,
                                this.secondarySelector.DesiredSize.Width,
                                this.secondarySelector.DesiredSize.Height));
            }

            if (this.tertiarySelector != null)
            {
                this.tertiarySelector.RectPosition =
                    this.tertiarySelector.TransformToVisual(this)
                        .TransformBounds(
                            new Rect(
                                0,
                                0,
                                this.tertiarySelector.DesiredSize.Width,
                                this.tertiarySelector.DesiredSize.Height));
            }
        }

        /// <summary>
        /// Make a focus or unfocus manually on each selector
        /// </summary>
        /// <param name="point">
        /// The point.
        /// </param>
        /// <param name="focusSourceType">
        /// The focus Source Type.
        /// </param>
        private void FocusPickerSelector(Point point, FocusSourceType focusSourceType)
        {
            if (this.primarySelector != null && point.X > this.primarySelector.RectPosition.X
               && point.X < (this.primarySelector.RectPosition.X + this.primarySelector.RectPosition.Width))
            {
                if (this.secondarySelector != null)
                {
                    this.secondarySelector.IsFocused = false;
                }

                if (this.tertiarySelector != null)
                {
                    this.tertiarySelector.IsFocused = false;
                }

                if (this.primarySelector != null)
                {
                    this.primarySelector.IsFocused = true;
                }

                return;
            }

            if (this.secondarySelector != null && point.X > this.secondarySelector.RectPosition.X
               && point.X < (this.secondarySelector.RectPosition.X + this.secondarySelector.RectPosition.Width))
            {
                if (this.primarySelector != null)
                {
                    this.primarySelector.IsFocused = false;
                }

                if (this.tertiarySelector != null)
                {
                    this.tertiarySelector.IsFocused = false;
                }

                if (this.secondarySelector != null)
                {
                    this.secondarySelector.IsFocused = true;
                }

                return;
            }

            if (this.tertiarySelector != null && point.X > this.tertiarySelector.RectPosition.X
               && point.X < (this.tertiarySelector.RectPosition.X + this.tertiarySelector.RectPosition.Width))
            {
                if (this.primarySelector != null)
                {
                    this.primarySelector.IsFocused = false;
                }

                if (this.secondarySelector != null)
                {
                    this.secondarySelector.IsFocused = false;
                }

                if (this.tertiarySelector != null)
                {
                    this.tertiarySelector.IsFocused = true;
                }
            }
        }

        #endregion

        #region Public Fields

        /// <summary>
        ///     The enable first tap has focus only property.
        /// </summary>
        public static readonly DependencyProperty EnableFirstTapHasFocusOnlyProperty =
            DependencyProperty.Register(
                "EnableFirstTapHasFocusOnly",
                typeof(bool),
                typeof(DatePicker),
                new PropertyMetadata(true));

        #endregion

        #region Internal Methods

        /// <summary>
        ///     The get selectors ordered by culture pattern.
        /// </summary>
        /// <returns>
        ///     The <see cref="IEnumerable" />.
        /// </returns>
        internal IEnumerable<PickerSelector> GetSelectorsOrderedByCulturePattern()
        {
            return this.GetSelectorsOrderedByCulturePattern(
                CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern,
                new[]
                    {
                        'y','M','d'
                    },
                new[]
                    {
                        this.primarySelector,this.secondarySelector,this.tertiarySelector
                    });
        }

        /// <summary>
        /// The get selectors ordered by culture pattern.
        /// </summary>
        /// <param name="pattern">
        /// The pattern.
        /// </param>
        /// <param name="patternCharacters">
        /// The pattern characters.
        /// </param>
        /// <param name="selectors">
        /// The selectors.
        /// </param>
        /// <returns>
        /// The <see cref="IEnumerable"/>.
        /// </returns>
        internal IEnumerable<PickerSelector> GetSelectorsOrderedByCulturePattern
            (string pattern, char[] patternCharacters, PickerSelector[] selectors)
        {
            if (null == pattern)
            {
                throw new ArgumentNullException("pattern");
            }

            if (null == patternCharacters)
            {
                throw new ArgumentNullException("patternCharacters");
            }

            if (null == selectors)
            {
                throw new ArgumentNullException("selectors");
            }

            if (patternCharacters.Length != selectors.Length)
            {
                throw new ArgumentException("Arrays must contain the same number of elements.");
            }

            // Create a list of index and selector pairs
            var pairs = new List<Tuple<int, PickerSelector>>(patternCharacters.Length);

            for (var i = 0; i < patternCharacters.Length; i++)
            {
                pairs.Add(new Tuple<int, PickerSelector>(pattern.IndexOf(patternCharacters[i]), selectors[i]));
            }

            // Return the corresponding selectors in order
            return pairs.Where(p => -1 != p.Item1).OrderBy(p => p.Item1).Select(p => p.Item2).Where(s => null != s);
        }

        #endregion
    }
}